﻿/*
 * KLF Headless Browser
 * Written by Kris Oye <kristianoye@gmail.com>
 * Copyright (C) 2011-2012.  All rights reserved.
 */
namespace KLF.Lib.Browser.DOM.HTML
{
    #region Using Statements

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using KLF.Lib.Browser.DOM.Events;

    #endregion

    public class HTMLSelectElement : HTMLControlBase
    {
        #region Constructors

        internal HTMLSelectElement(Document owner)
            : base(owner, "select")
        {
        }

        #endregion

        #region DOM Level 1 Compliance

        public override void setAttribute(string name, string value)
        {
            if (name.Equals("multiple"))
            {
                bool flag;

                if (Boolean.TryParse(value, out flag))
                {
                    multiple = flag;
                    return;
                }
                else
                {
                    multiple = value.Contains("multiple");
                    return;
                }
            }
            base.setAttribute(name, value);
        }

        #endregion

        #region DOM Level 2 HTML Compliance

        /// <summary>
        /// Add a new element to the collection of OPTION elements for this 
        /// SELECT. This method is the equivalent of the appendChild method of
        /// the Node interface if the before parameter is null. It is 
        /// equivalent to the insertBefore method on the parent of before in 
        /// all other cases. This method may have no effect if the new element
        /// is not an OPTION or an OPTGROUP. 
        /// </summary>
        /// <param name="element"></param>
        /// <param name="before"></param>
        public void add(HTMLElement element, HTMLElement before)
        {
            if (element is HTMLOptGroupElement || element is HTMLOptionElement)
            {
                if (before == null)
                {
                    appendChild(element);
                }
                else
                {
                    insertBefore(element, before);
                }
            }
        }

        /// <summary>
        /// The number of options in this SELECT.
        /// </summary>
        public int length
        {
            get
            {
                return options.length;
            }
            set
            {
                throw new DOMException(DOMException.NOT_SUPPORTED_ERR);
            }
        }

        /// <summary>
        /// Spec says one thing, implementors do another.
        /// 
        /// If true, multiple OPTION elements may be selected in this SELECT. 
        /// See the multiple attribute definition in HTML 4.01.
        /// </summary>
        public bool multiple 
        {
            get
            {
                return HTMLBool.Eval(
                    getAttribute(HTMLConstants.HTML_ATT_MULTIPLE),
                    HTMLConstants.HTML_ATT_MULTIPLE);
            }
            set
            {
                if (!value)
                    removeAttribute(HTMLConstants.HTML_ATT_MULTIPLE);
                else
                    setAttribute(
                        HTMLConstants.HTML_ATT_MULTIPLE,
                        HTMLConstants.HTML_ATT_MULTIPLE);
            }
        }

        /// <summary>
        /// Returns an enumerable list of options.
        /// </summary>
        internal List<HTMLOptionElement> Options
        {
            get
            {
                return new List<HTMLOptionElement>(Elements
                    .Where(e => (e as HTMLOptionElement) != null)
                    .Cast<HTMLOptionElement>());
            }
        }

        /// <summary>
        /// The collection of OPTION elements contained by this element.
        /// </summary>
        public HTMLOptionsCollection options
        {
            get
            {
                return new HTMLOptionsCollection(Owner, Elements
                    .Where(e => (e as HTMLOptionElement) != null)
                    .Cast<Node>());
            }
        }

        /// <summary>
        /// Remove an element from the collection of OPTION elements for this
        /// SELECT. Does nothing if no element has the given index.
        /// </summary>
        /// <param name="index"></param>
        public void remove(int index)
        {
            HTMLOptionsCollection opts = options;

            if (index > -1 && index < opts.length)
            {
                removeChild(opts.item(index));
            }
        }

        /// <summary>
        /// The ordinal index of the selected option, starting from 0. The 
        /// value -1 is returned if no element is selected. If multiple 
        /// options are selected, the index of the first selected option 
        /// is returned.
        /// </summary>
        public int selectedIndex
        {
            get
            {
                var opts = Options;

                for (int i = 0; i < opts.Count; i++)
                {
                    if (HTMLBool.Eval(opts[i]
                        .getAttribute(
                            HTMLConstants.HTML_ATT_SELECTED),
                            HTMLConstants.HTML_ATT_SELECTED)) return i;
                }

                return -1;
            }
            set
            {
                var opts = Options;

                if (value < opts.Count)
                {
                    for (int i = 0; i < opts.Count; i++)
                        if (value == i)
                        {
                            opts[i].setAttribute(
                                HTMLConstants.HTML_ATT_SELECTED,
                                HTMLConstants.HTML_ATT_SELECTED);
                            selectedItem = opts[i];
                        }
                        else
                            opts[i].removeAttribute(
                                HTMLConstants.HTML_ATT_SELECTED);
                }
            }
        }

        /// <summary>
        /// Returns 
        /// </summary>
        public HTMLOptionElement selectedItem
        {
            get
            {
                if (multiple)
                    return null;

                var o = Options;

                return (o != null && selectedIndex > -1 ? o[selectedIndex] : 
                    o != null && o.Count > 0 ? o.First() : null);
            }
            set
            {
                //  No change
                if (selectedItem == value)
                    return;

                if (!multiple)
                {
                    foreach (HTMLOptionElement o in Options)
                    {
                        if (o == value)
                            o.setAttribute(
                                HTMLConstants.HTML_ATT_SELECTED,
                                HTMLConstants.HTML_ATT_SELECTED);
                        else
                            o.removeAttribute(
                                HTMLConstants.HTML_ATT_SELECTED);
                    }
                }

                Event onChange = Owner.createEvent(HTMLConstants.HTML_EVENT_HTMLEVENTS);
                onChange.initEvent(
                    HTMLConstants.HTML_EVENT_CHANGE,
                    true,
                    false);
                onChange.target = this;
                dispatchEvent(onChange);
            }
        }

        /// <summary>
        /// Number of visible rows. See the size attribute definition 
        /// in HTML 4.01.
        /// </summary>
        public int size
        {
            get
            {
                int size;

                if (!Int32.TryParse(getAttribute(HTMLConstants.HTML_ATT_SIZE), out size))
                    return 1;
                return size > 0 ? size : 1;
            }
            set
            {
                int size;

                if (Int32.TryParse(getAttribute(HTMLConstants.HTML_ATT_SIZE), out size))
                {
                    base.setAttribute(HTMLConstants.HTML_ATT_SIZE, size.ToString());
                }
            }
        }

        /// <summary>
        /// The type of this form control. This is the string "select-multiple"
        /// when the multiple attribute is true and the string "select-one" 
        /// when false.
        /// </summary>
        public string type
        {
            get
            {
                return getAttribute(HTMLConstants.HTML_ATT_TYPE);
            }
            set
            {
                setAttribute(HTMLConstants.HTML_ATT_TYPE, value);
            }
        }

        /// <summary>
        /// The current form control value (i.e. the value of the currently 
        /// selected option), if multiple options are selected this is the 
        /// value of the first selected option.
        /// </summary>
        public override string value
        {
            get
            {
                HTMLOptionElement o = selectedItem;

                return o != null ? o.value : "";
            }
            set
            {
                HTMLOptionsCollection opts = options;

                foreach (HTMLOptionElement o in Options)
                {
                    if (o.value == value)
                    {
                        selectedItem = o;
                        break;
                    }
                    else if (o.text.Contains(value))
                    {
                        selectedItem = o;
                        break;
                    }
                }
            }
        }

        #endregion

        #region HTML DOM Events

        internal override void SimulateInput(string val)
        {
            value = val;
        }

        #endregion
    }
}
